# -*-coding:utf-8-*-
import random
import sys


class Card(object):
    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name__}(suit={suit!r},rank={rank!r})". \
            format(___class__=self.__class__, **self.__dict__)

    def __str__(self):
        return "{rank}{suit}".format(**self.__dict__)

    def __format__(self, format_spec):
        if format_spec == "":
            return str(self)
        rs = format_spec.replace("%r", self.rank).replace("%s", self.suit.name)
        rs = rs.replace("%%", "%")
        return rs

    def __bytes__(self):
        class_code = self.__class__.__name__[0]
        rank_number_str = {'A': '1', 'J': '11', 'Q': '12', 'K': '13'}.get(self.rank, self.rank)
        string_ = "(" + " ".join([class_code, rank_number_str, self.suit.name, ]) + ")"
        return bytes(string_, encoding='utf8')


def card_from_bytes(buffer):
    string = buffer.decode("utf8")
    assert string[0] == "(" and string[-1] == ")"
    code, rank_number, suit = string[1:-1].split()
    class_ = {'A': AceCard, 'N': NumberCard, 'F': FaceCard}[code]
    return class_(int(rank_number), suit)


class Card2(object):
    insure = False

    def __init__(self, rank, suit, hand, soft):
        self.rank = rank
        self.suit = suit
        self.hand = hand
        self.soft = soft

    def __repr__(self):
        return "{__class__.__name___}(suit={suit!r},rank={rank!r})". \
            format(__class__=self.__class__, **self.__dict__)

    def __eq__(self, other):
        return self.suit == other.suit and self.rank == other.rank

    def __hash__(self):
        return hash(self.suit) ^ hash(self.rank)


class AceCard2(Card2):
    insure = True

    def __init__(self, rank, suit):
        super(AceCard2, self).__init__("A", suit, 1, 11)


class NumberCard(Card):
    def __init__(self, rank, suit):
        super(NumberCard, self).__init__(str(rank), suit, rank, rank)


class AceCard(Card):
    def __init__(self, rank, suit):
        super(AceCard, self).__init__("A", suit, 1, 11)


class FaceCard(Card):
    def __init__(self, rank, suit):
        super(FaceCard, self).__init__({11: 'J', 12: 'Q', 13: 'K'}[rank], suit, 10, 10)


class Suit:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol


def card(rank, suit):
    if rank == 1:
        return AceCard('A', suit)
    elif 2 <= rank < 11:
        return NumberCard(str(rank), suit)
    elif 11 <= rank < 14:
        name = {11: 'J', 12: 'Q', 13: 'K'}[rank]
        return FaceCard(name, suit)
    else:
        return Exception("Rank out of range")


class CardFactory:
    def rank(self, rank):
        self.class_, self.rank_str = {
            1: (AceCard, 'A'),
            11: (FaceCard, 'J'),
            12: (FaceCard, 'Q'),
            13: (FaceCard, 'K'),
        }.get(rank, (NumberCard, str(rank)))
        return self

    def suit(self, suit):
        return self.class_(self.rank_str, suit)


def card10(rank, suit):
    if rank == 1:
        return AceCard(rank, suit)
    elif 2 <= rank < 11:
        return NumberCard(rank, suit)
    elif 11 <= rank < 14:
        return FaceCard(rank, suit)
    else:
        raise Exception("Rank out of range")


def card6(rank, suit):
    class_, rank_str = {
        1: (AceCard, 'A'),
        11: (FaceCard, 'J'),
        12: (FaceCard, 'Q'),
        13: (FaceCard, 'K'),
    }.get(rank, (NumberCard, str(rank)))
    return class_(rank_str, suit)


class Deck:
    def __init__(self):
        self._cards = [card10(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]
        random.shuffle(self._cards)

    def pop(self):
        return self._cards.pop()


class Deck2(list):
    def __init__(self):
        super(Deck2, self).__init__(card10(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
        random.shuffle(self)


class Deck3(list):
    def __init__(self, decks=1):
        super(Deck3, self).__init__()
        for i in range(decks):
            self.extend(card10(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
        random.shuffle(self)
        burn = random.randint(1, 52)
        for i in range(burn): self.pop()


# class Hand:
#     def __init__(self, dealer_card):
#         self.dealer_card = dealer_card
#         self.cards = []
#
#     def hard_total(self):
#         return sum(c.hard for c in self.cards)
#
#     def soft_total(self):
#         return sum(c.soft for c in self.cards)
# class Hand(object):
#     def __init__(self, dealer_card, *cards):
#         self.dealer_card = dealer_card
#         self.cards = list(cards)
#
#     def __str__(self):
#         return ", ".join(map(str, self.cards))
#
#     def __repr__(self):
#         return "{__class__.__name__}({dealer_card!r},{_cards_str})" \
#             .format(___class__=self.__class__,
#                     _cards_str=", ".join(map(repr, self.cards)),
#                     **self.__dict__)
#
#     def __eq__(self, other):
#         return self.cards == other.cards and self.dealer_card == other.dealer_card
#
#     __hash__ = None
class Hand:
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.cards = list(cards)

    def __str__(self):
        return ", ".join(map(str, self.cards))

    def __repr__(self):
        return "{__class__.__name__}({dealer_card!r},{_cards_str})".format(
            __class__=self.__class__,
            _cards_str=", ".join(map(repr, self.cards)),
            **self.__dict__)

    def __eq__(self, other):
        if isinstance(other, int):
            return self.total() == other
        try:
            return (self.cards == other.cards
                    and self.dealer_card == other.dealer_card)
        except AttributeError:
            return NotImplemented

    def __lt__(self, other):
        if isinstance(other, int):
            return self.total() < other
        try:
            return self.total() < other.total()
        except AttributeError:
            return NotImplemented

    def __len__(self, other):
        if isinstance(other, int):
            return self.total() <= other.total()
        try:
            return self.total() <= other.total()
        except AttributeError:
            return NotImplemented

    __hash__ = None

    def total(self):
        delta_soft = max(c.soft - c.hard for c in self.cards)
        hard = sum(c.hard for c in self.cards)
        if hard + delta_soft <= 21: return hard + delta_soft
        return hard


class FrozenHand(Hand):
    def __init__(self, *args, **kw):
        if len(args) == 1 and isinstance(args[0], Hand):
            other = args[0]
            self.dealer_card = other.dealer_card
            self.cards = other.cards
        else:
            super(FrozenHand, self).__init__(*args, **kw)


class Hand2:
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.cards = list(cards)

    def hard_total(self):
        return sum(c.hard for c in self.cards)

    def soft_total(self):
        return sum(c.soft for c in self.cards)


class Hand3:
    def __init__(self, *args, **kw):
        if len(args) == 1 and isinstance(args[0], Hand3):
            other = args[0]
            self.dealer_card = other.dealer_card
            self.cards = other.cards
        else:
            dealer_card = args[0]
            cards = args[1:]
            self.dealer_card = dealer_card
            self.cards = list(cards)


class Hand4:
    def __init__(self, *args, **kw):
        if len(args) == 1 and isinstance(args[0], Hand4):
            other = args[0]
            self.dealer_card = other.dealer_card
            self.cards = other.cards
        elif len(args) == 2 and isinstance(args[0], Hand4) and 'split' in kw:
            other, card = args
            self.dealer_card = other.dealer_card
            self.cards = [other.cards[kw['split']], card]
        elif len(args) == 3:
            dealer_card = args[0]
            cards = args[1:]
            self.dealer_card = dealer_card
            self.cards = list(cards)
        else:
            raise TypeError("Invalid construactor args={0!r} kw={1!r}".format(args, kw))

    def __str__(self):
        return ", ".join(map(str, self.cards))


class GameStrategy:
    def insurance(self, hand):
        return False

    def split(self, hand):
        return False

    def double(self, hand):
        return False

    def hit(self, hand):
        return sum(c.hard for c in hand.cards) <= 17


class Table:
    def __init__(self):
        self.deck = Deck()

    def place_bet(self, amount):
        print("Bet", amount)

    def get_hand(self):
        try:
            self.hand = Hand2(d.pop(), d.pop(), d.pop())
            self.hole_card = d.pop()
        except IndexError:
            self.deck = Deck()
            return self.get_hand()
        print("Deal", self.hand)
        return self.hand

    def can_insure(self, hand):
        return hand.dealer_card.insure


class Hand5:
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.cards = list(cards)

    @staticmethod
    def freeze(other):
        hand = Hand5(other.dealer_card, *other.cards)
        return hand

    @staticmethod
    def split(other, card0, card1):
        hand0 = Hand5(other.dealer_card, other.card[0], card0)
        hand1 = Hand5(other.dealer_card, other.card[1], card1)
        return hand0, hand1

    def __str__(self):
        return ", ".join(map(str, self.cards))


# class BlackJackCard_p:
#     def __init__(self, rank, suit):
#         self.rank = rank
#         self.suit = suit
#
#     def __lt__(self, other):
#         print("Compare {0}<{1}".format(self, other))
#         return self.rank < other.rank
#     def __str__(self):
#         return "{rank}{suit}".format(**self.__dict__)
class BlackJackCard:
    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft

    def __lt__(self, other):
        if not isinstance(other, BlackJackCard): return NotImplemented
        return self.rank < other.rank

    def __le__(self, other):
        try:
            return self.rank <= other.rank
        except AttributeError:
            return NotImplemented

    def __gt__(self, other):
        if not isinstance(other, BlackJackCard): return NotImplemented

    def __ge__(self, other):
        if not isinstance(other, BlackJackCard): return NotImplemented

    def __eq__(self, other):
        if not isinstance(other, BlackJackCard): return NotImplemented
        return self.rank == other.rank and self.suit == other.suit

    def __ne__(self, other):
        if not isinstance(other, BlackJackCard): return NotImplemented
        return self.rank != other.rank and self.suit != other.suit

    def __str__(self):
        return "{rank}{suit}".format(**self.__dict__)


class BNumberCard(BlackJackCard):
    def __init__(self, rank, suit):
        super().__init__(str(rank), suit, rank, rank)


class BAceCard(BlackJackCard):
    def __init__(self, rank, suit):
        super().__init__("A", suit, 1, 11)


class BFaceCard(BlackJackCard):
    def __init__(self, rank, suit):
        super().__init__({11: 'J', 12: 'Q', 13: 'K'}[rank], suit, 10, 10)


def card21(rank, suit):
    if rank == 1:
        return BAceCard(rank, suit)
    elif 2 <= rank < 11:
        return BNumberCard(rank, suit)
    elif 11 <= rank < 14:
        return BFaceCard(rank, suit)
    else:
        raise Exception("Rank out of range")


if __name__ == '__main__':
    Club, Diamond, Heart, Spade = Suit('Club', '♣'), Suit('Diamond', '♦'), Suit('Heart', '♥'), Suit('Spade', '♠')
    d = Deck()
    SCard_ = d.pop()
    b = bytes(SCard_)
    someCard = card_from_bytes(b)
    pass
